/*
 * 功能：自定义拦截器规则，通过 token 验证请求
 * 作者：张杨(Sunny)
 * 日期： 2024/10/26 上午10:24
 */
package com.example.springboot.common;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.example.springboot.entity.User;
import com.example.springboot.exception.ServiceException;
import com.example.springboot.mapper.UserMapper;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * JwtInterceptor 类用于拦截请求并验证 JWT token
 * 这是个Spring拦截器，用于在请求到达控制器前进行JWT认证
 */
public class JwtInterceptor implements HandlerInterceptor {

    @Resource
    private UserMapper userMapper;

    /**
     * 在请求处理之前进行调用，用于验证用户的 JWT token
     *
     * @param request  HTTP请求对象
     * @param response HTTP响应对象
     * @param handler  处理请求的处理器对象
     * @return 如果验证通过返回true，否则抛出异常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 1. 从请求头获取token
        String token = request.getHeader("token"); // 前端的header传过来的参数(请求头)

        // 2. 如果请求头没有token，则尝试从URL参数获取
        if (StrUtil.isBlank(token)) {
            token = request.getParameter("token");
        }

        // 3. 处理OPTIONS预检请求，直接放行
        // 这是为了支持跨域请求，浏览器会先发送OPTIONS请求
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            return true;
        }

        // 4. 检查方法是否有AuthAccess注解，有则放行
        // 用于标记不需要认证的接口方法
        if (handler instanceof HandlerMethod) {
            AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class);
            if (annotation != null) {
                return true;
            }
        }

        // 5. 检查token是否存在，不存在则抛出异常
        if (StrUtil.isBlank(token)) {
            throw new ServiceException("401", "请先登录");
        }

        // 6. 解码token获取用户ID
        String userId;
        try {
            // 使用JWT库解码token，获取audience(受众)中的第一个值作为用户ID
            userId = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException j) {
            throw new ServiceException("401", "token 无效，请重新登录");
        }

        // 7. 根据用户ID查询数据库验证用户是否存在
        User user = userMapper.selectById(Integer.valueOf(userId));
        if (user == null) {
            throw new ServiceException("401", "用户不存在，请重新登录");
        }

        // 8. 使用用户密码作为密钥创建JWT验证器
        // HMAC256算法使用用户密码作为密钥，确保只有知道密码的人能生成有效token
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
        try {
            // 验证token是否有效（是否被篡改、是否过期等）
            jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
            throw new ServiceException("401", "token 验证失败，请重新登录");
        }

        // 9. 所有验证通过，允许请求继续处理
        return true;
    }
}
